// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "cc/animation/animation_host.h"

#include <algorithm>

#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/trace_event/trace_event.h"
#include "base/trace_event/trace_event_argument.h"
#include "cc/animation/animation_delegate.h"
#include "cc/animation/animation_events.h"
#include "cc/animation/animation_id_provider.h"
#include "cc/animation/animation_player.h"
#include "cc/animation/animation_timeline.h"
#include "cc/animation/element_animations.h"
#include "cc/animation/scroll_offset_animation_curve.h"
#include "cc/animation/scroll_offset_animations.h"
#include "cc/animation/scroll_offset_animations_impl.h"
#include "cc/animation/timing_function.h"
#include "ui/gfx/geometry/box_f.h"
#include "ui/gfx/geometry/scroll_offset.h"

namespace cc {

std::unique_ptr<AnimationHost> AnimationHost::CreateMainInstance()
{
    return base::WrapUnique(new AnimationHost(ThreadInstance::MAIN));
}

std::unique_ptr<AnimationHost> AnimationHost::CreateForTesting(
    ThreadInstance thread_instance)
{
    auto animation_host = base::WrapUnique(new AnimationHost(thread_instance));

    if (thread_instance == ThreadInstance::IMPL)
        animation_host->SetSupportsScrollAnimations(true);

    return animation_host;
}

AnimationHost::AnimationHost(ThreadInstance thread_instance)
    : mutator_host_client_(nullptr)
    , thread_instance_(thread_instance)
    , supports_scroll_animations_(false)
    , needs_push_properties_(false)
{
    if (thread_instance_ == ThreadInstance::IMPL) {
        scroll_offset_animations_impl_ = base::MakeUnique<ScrollOffsetAnimationsImpl>(this);
    } else {
        scroll_offset_animations_ = base::MakeUnique<ScrollOffsetAnimations>(this);
    }
}

AnimationHost::~AnimationHost()
{
    scroll_offset_animations_impl_ = nullptr;

    ClearMutators();
    DCHECK(!mutator_host_client());
    DCHECK(element_to_animations_map_.empty());
}

std::unique_ptr<MutatorHost> AnimationHost::CreateImplInstance(
    bool supports_impl_scrolling) const
{
    DCHECK_EQ(thread_instance_, ThreadInstance::MAIN);

    auto mutator_host_impl = base::WrapUnique<MutatorHost>(new AnimationHost(ThreadInstance::IMPL));
    mutator_host_impl->SetSupportsScrollAnimations(supports_impl_scrolling);
    return mutator_host_impl;
}

AnimationTimeline* AnimationHost::GetTimelineById(int timeline_id) const
{
    auto f = id_to_timeline_map_.find(timeline_id);
    return f == id_to_timeline_map_.end() ? nullptr : f->second.get();
}

void AnimationHost::ClearMutators()
{
    for (auto& kv : id_to_timeline_map_)
        EraseTimeline(kv.second);
    id_to_timeline_map_.clear();
}

void AnimationHost::EraseTimeline(scoped_refptr<AnimationTimeline> timeline)
{
    timeline->ClearPlayers();
    timeline->SetAnimationHost(nullptr);
}

void AnimationHost::AddAnimationTimeline(
    scoped_refptr<AnimationTimeline> timeline)
{
    DCHECK(timeline->id());
    timeline->SetAnimationHost(this);
    id_to_timeline_map_.insert(
        std::make_pair(timeline->id(), std::move(timeline)));
    SetNeedsPushProperties();
}

void AnimationHost::RemoveAnimationTimeline(scoped_refptr<AnimationTimeline> timeline)
{
    DCHECK(timeline->id());
    EraseTimeline(timeline);
    id_to_timeline_map_.erase(timeline->id());
    SetNeedsPushProperties();
}

void AnimationHost::RegisterElement(ElementId element_id, ElementListType list_type)
{
    scoped_refptr<ElementAnimations> element_animations = GetElementAnimationsForElementId(element_id);
    if (element_animations)
        element_animations->ElementRegistered(element_id, list_type);
}

void AnimationHost::UnregisterElement(ElementId element_id, ElementListType list_type)
{
    scoped_refptr<ElementAnimations> element_animations = GetElementAnimationsForElementId(element_id);
    if (element_animations)
        element_animations->ElementUnregistered(element_id, list_type);
}

void AnimationHost::RegisterPlayerForElement(ElementId element_id, AnimationPlayer* player)
{
    DCHECK(element_id);
    DCHECK(player);

    scoped_refptr<ElementAnimations> element_animations = GetElementAnimationsForElementId(element_id);
    if (!element_animations) {
        element_animations = ElementAnimations::Create();
        element_animations->SetElementId(element_id);
        element_to_animations_map_[element_animations->element_id()] = element_animations;
    }

    if (element_animations->animation_host() != this) {
        element_animations->SetAnimationHost(this);
        element_animations->InitAffectedElementTypes();
    }

    element_animations->AddPlayer(player);
}

void AnimationHost::UnregisterPlayerForElement(ElementId element_id, AnimationPlayer* player)
{
    DCHECK(element_id);
    DCHECK(player);

    scoped_refptr<ElementAnimations> element_animations = GetElementAnimationsForElementId(element_id);
    DCHECK(element_animations);
    element_animations->RemovePlayer(player);

    if (element_animations->IsEmpty()) {
        element_animations->ClearAffectedElementTypes();
        element_to_animations_map_.erase(element_animations->element_id());
        element_animations->SetAnimationHost(nullptr);
    }

    RemoveFromTicking(player);
}

void AnimationHost::SetMutatorHostClient(MutatorHostClient* client)
{
    if (mutator_host_client_ == client)
        return;

    mutator_host_client_ = client;

    if (needs_push_properties() && mutator_host_client())
        SetNeedsPushProperties();
}

void AnimationHost::SetNeedsCommit()
{
    DCHECK(mutator_host_client_);
    mutator_host_client_->SetMutatorsNeedCommit();
    // TODO(loyso): Invalidate property trees only if really needed.
    mutator_host_client_->SetMutatorsNeedRebuildPropertyTrees();
}

void AnimationHost::SetNeedsPushProperties()
{
    needs_push_properties_ = true;
}

void AnimationHost::PushPropertiesTo(MutatorHost* mutator_host_impl)
{
    auto host_impl = static_cast<AnimationHost*>(mutator_host_impl);

    if (needs_push_properties_) {
        needs_push_properties_ = false;
        PushTimelinesToImplThread(host_impl);
        RemoveTimelinesFromImplThread(host_impl);
        PushPropertiesToImplThread(host_impl);
        // This is redundant but used in tests.
        host_impl->needs_push_properties_ = false;
    }
}

void AnimationHost::PushTimelinesToImplThread(AnimationHost* host_impl) const
{
    for (auto& kv : id_to_timeline_map_) {
        auto& timeline = kv.second;
        AnimationTimeline* timeline_impl = host_impl->GetTimelineById(timeline->id());
        if (timeline_impl)
            continue;

        scoped_refptr<AnimationTimeline> to_add = timeline->CreateImplInstance();
        host_impl->AddAnimationTimeline(to_add.get());
    }
}

void AnimationHost::RemoveTimelinesFromImplThread(
    AnimationHost* host_impl) const
{
    IdToTimelineMap& timelines_impl = host_impl->id_to_timeline_map_;

    // Erase all the impl timelines which |this| doesn't have.
    for (auto it = timelines_impl.begin(); it != timelines_impl.end();) {
        auto& timeline_impl = it->second;
        if (timeline_impl->is_impl_only() || GetTimelineById(timeline_impl->id())) {
            ++it;
        } else {
            host_impl->EraseTimeline(it->second);
            it = timelines_impl.erase(it);
        }
    }
}

void AnimationHost::PushPropertiesToImplThread(AnimationHost* host_impl)
{
    // Sync all players with impl thread to create ElementAnimations. This needs
    // to happen before the element animations are synced below.
    for (auto& kv : id_to_timeline_map_) {
        AnimationTimeline* timeline = kv.second.get();
        if (timeline->needs_push_properties()) {
            AnimationTimeline* timeline_impl = host_impl->GetTimelineById(timeline->id());
            if (timeline_impl)
                timeline->PushPropertiesTo(timeline_impl);
        }
    }

    // Sync properties for created ElementAnimations.
    for (auto& kv : element_to_animations_map_) {
        const auto& element_animations = kv.second;
        if (element_animations->needs_push_properties()) {
            auto element_animations_impl = host_impl->GetElementAnimationsForElementId(kv.first);
            if (element_animations_impl)
                element_animations->PushPropertiesTo(
                    std::move(element_animations_impl));
        }
    }

    // Update the impl-only scroll offset animations.
    scroll_offset_animations_->PushPropertiesTo(host_impl->scroll_offset_animations_impl_.get());
}

scoped_refptr<ElementAnimations> AnimationHost::GetElementAnimationsForElementId(ElementId element_id) const
{
    if (!element_id)
        return nullptr;
    auto iter = element_to_animations_map_.find(element_id);
    return iter == element_to_animations_map_.end() ? nullptr : iter->second;
}

void AnimationHost::SetSupportsScrollAnimations(bool supports_scroll_animations)
{
    supports_scroll_animations_ = supports_scroll_animations;
}

bool AnimationHost::SupportsScrollAnimations() const
{
    return supports_scroll_animations_;
}

bool AnimationHost::NeedsTickAnimations() const
{
    return !ticking_players_.empty();
}

bool AnimationHost::ActivateAnimations()
{
    if (!NeedsTickAnimations())
        return false;

    TRACE_EVENT0("cc", "AnimationHost::ActivateAnimations");
    PlayersList ticking_players_copy = ticking_players_;
    for (auto& it : ticking_players_copy)
        it->ActivateAnimations();

    return true;
}

bool AnimationHost::TickAnimations(base::TimeTicks monotonic_time)
{
    if (!NeedsTickAnimations())
        return false;

    TRACE_EVENT0("cc", "AnimationHost::TickAnimations");
    PlayersList ticking_players_copy = ticking_players_;
    for (auto& it : ticking_players_copy)
        it->Tick(monotonic_time);

    return true;
}

bool AnimationHost::UpdateAnimationState(bool start_ready_animations, MutatorEvents* mutator_events)
{
    if (!NeedsTickAnimations())
        return false;

    auto animation_events = static_cast<AnimationEvents*>(mutator_events);

    TRACE_EVENT0("cc", "AnimationHost::UpdateAnimationState");
    PlayersList ticking_players_copy = ticking_players_;
    for (auto& it : ticking_players_copy)
        it->UpdateState(start_ready_animations, animation_events);

    return true;
}

std::unique_ptr<MutatorEvents> AnimationHost::CreateEvents()
{
    return base::MakeUnique<AnimationEvents>();
}

void AnimationHost::SetAnimationEvents(
    std::unique_ptr<MutatorEvents> mutator_events)
{
    auto events = base::WrapUnique(static_cast<AnimationEvents*>(mutator_events.release()));

    for (size_t event_index = 0; event_index < events->events_.size();
         ++event_index) {
        ElementId element_id = events->events_[event_index].element_id;

        // Use the map of all ElementAnimations, not just ticking players, since
        // non-ticking Players may still receive events for impl-only animations.
        const ElementToAnimationsMap& all_element_animations = element_to_animations_map_;
        auto iter = all_element_animations.find(element_id);
        if (iter != all_element_animations.end()) {
            switch (events->events_[event_index].type) {
            case AnimationEvent::STARTED:
                (*iter).second->NotifyAnimationStarted(events->events_[event_index]);
                break;

            case AnimationEvent::FINISHED:
                (*iter).second->NotifyAnimationFinished(events->events_[event_index]);
                break;

            case AnimationEvent::ABORTED:
                (*iter).second->NotifyAnimationAborted(events->events_[event_index]);
                break;

            case AnimationEvent::PROPERTY_UPDATE:
                (*iter).second->NotifyAnimationPropertyUpdate(
                    events->events_[event_index]);
                break;

            case AnimationEvent::TAKEOVER:
                (*iter).second->NotifyAnimationTakeover(events->events_[event_index]);
                break;
            }
        }
    }
}

bool AnimationHost::ScrollOffsetAnimationWasInterrupted(
    ElementId element_id) const
{
    auto element_animations = GetElementAnimationsForElementId(element_id);
    return element_animations
        ? element_animations->ScrollOffsetAnimationWasInterrupted()
        : false;
}

bool AnimationHost::IsAnimatingFilterProperty(ElementId element_id,
    ElementListType list_type) const
{
    auto element_animations = GetElementAnimationsForElementId(element_id);
    return element_animations
        ? element_animations->IsCurrentlyAnimatingProperty(
            TargetProperty::FILTER, list_type)
        : false;
}

bool AnimationHost::IsAnimatingOpacityProperty(
    ElementId element_id,
    ElementListType list_type) const
{
    auto element_animations = GetElementAnimationsForElementId(element_id);
    return element_animations
        ? element_animations->IsCurrentlyAnimatingProperty(
            TargetProperty::OPACITY, list_type)
        : false;
}

bool AnimationHost::IsAnimatingTransformProperty(
    ElementId element_id,
    ElementListType list_type) const
{
    auto element_animations = GetElementAnimationsForElementId(element_id);
    return element_animations
        ? element_animations->IsCurrentlyAnimatingProperty(
            TargetProperty::TRANSFORM, list_type)
        : false;
}

bool AnimationHost::HasPotentiallyRunningFilterAnimation(
    ElementId element_id,
    ElementListType list_type) const
{
    auto element_animations = GetElementAnimationsForElementId(element_id);
    return element_animations
        ? element_animations->IsPotentiallyAnimatingProperty(
            TargetProperty::FILTER, list_type)
        : false;
}

bool AnimationHost::HasPotentiallyRunningOpacityAnimation(
    ElementId element_id,
    ElementListType list_type) const
{
    auto element_animations = GetElementAnimationsForElementId(element_id);
    return element_animations
        ? element_animations->IsPotentiallyAnimatingProperty(
            TargetProperty::OPACITY, list_type)
        : false;
}

bool AnimationHost::HasPotentiallyRunningTransformAnimation(
    ElementId element_id,
    ElementListType list_type) const
{
    auto element_animations = GetElementAnimationsForElementId(element_id);
    return element_animations
        ? element_animations->IsPotentiallyAnimatingProperty(
            TargetProperty::TRANSFORM, list_type)
        : false;
}

bool AnimationHost::HasAnyAnimationTargetingProperty(
    ElementId element_id,
    TargetProperty::Type property) const
{
    auto element_animations = GetElementAnimationsForElementId(element_id);
    if (!element_animations)
        return false;

    return element_animations->HasAnyAnimationTargetingProperty(property);
}

bool AnimationHost::HasFilterAnimationThatInflatesBounds(
    ElementId element_id) const
{
    auto element_animations = GetElementAnimationsForElementId(element_id);
    return element_animations
        ? element_animations->HasFilterAnimationThatInflatesBounds()
        : false;
}

bool AnimationHost::HasTransformAnimationThatInflatesBounds(
    ElementId element_id) const
{
    auto element_animations = GetElementAnimationsForElementId(element_id);
    return element_animations
        ? element_animations->HasTransformAnimationThatInflatesBounds()
        : false;
}

bool AnimationHost::HasAnimationThatInflatesBounds(ElementId element_id) const
{
    auto element_animations = GetElementAnimationsForElementId(element_id);
    return element_animations
        ? element_animations->HasAnimationThatInflatesBounds()
        : false;
}

bool AnimationHost::FilterAnimationBoundsForBox(ElementId element_id,
    const gfx::BoxF& box,
    gfx::BoxF* bounds) const
{
    auto element_animations = GetElementAnimationsForElementId(element_id);
    return element_animations
        ? element_animations->FilterAnimationBoundsForBox(box, bounds)
        : false;
}

bool AnimationHost::TransformAnimationBoundsForBox(ElementId element_id,
    const gfx::BoxF& box,
    gfx::BoxF* bounds) const
{
    *bounds = gfx::BoxF();
    auto element_animations = GetElementAnimationsForElementId(element_id);
    return element_animations
        ? element_animations->TransformAnimationBoundsForBox(box, bounds)
        : true;
}

bool AnimationHost::HasOnlyTranslationTransforms(
    ElementId element_id,
    ElementListType list_type) const
{
    auto element_animations = GetElementAnimationsForElementId(element_id);
    return element_animations
        ? element_animations->HasOnlyTranslationTransforms(list_type)
        : true;
}

bool AnimationHost::AnimationsPreserveAxisAlignment(
    ElementId element_id) const
{
    auto element_animations = GetElementAnimationsForElementId(element_id);
    return element_animations
        ? element_animations->AnimationsPreserveAxisAlignment()
        : true;
}

bool AnimationHost::MaximumTargetScale(ElementId element_id,
    ElementListType list_type,
    float* max_scale) const
{
    *max_scale = 0.f;
    auto element_animations = GetElementAnimationsForElementId(element_id);
    return element_animations
        ? element_animations->MaximumTargetScale(list_type, max_scale)
        : true;
}

bool AnimationHost::AnimationStartScale(ElementId element_id,
    ElementListType list_type,
    float* start_scale) const
{
    *start_scale = 0.f;
    auto element_animations = GetElementAnimationsForElementId(element_id);
    return element_animations
        ? element_animations->AnimationStartScale(list_type, start_scale)
        : true;
}

bool AnimationHost::HasAnyAnimation(ElementId element_id) const
{
    auto element_animations = GetElementAnimationsForElementId(element_id);
    return element_animations ? element_animations->HasAnyAnimation() : false;
}

bool AnimationHost::HasTickingAnimationForTesting(ElementId element_id) const
{
    auto element_animations = GetElementAnimationsForElementId(element_id);
    return element_animations ? element_animations->HasTickingAnimation() : false;
}

void AnimationHost::ImplOnlyScrollAnimationCreate(
    ElementId element_id,
    const gfx::ScrollOffset& target_offset,
    const gfx::ScrollOffset& current_offset,
    base::TimeDelta delayed_by)
{
    DCHECK(scroll_offset_animations_impl_);
    scroll_offset_animations_impl_->ScrollAnimationCreate(
        element_id, target_offset, current_offset, delayed_by);
}

bool AnimationHost::ImplOnlyScrollAnimationUpdateTarget(
    ElementId element_id,
    const gfx::Vector2dF& scroll_delta,
    const gfx::ScrollOffset& max_scroll_offset,
    base::TimeTicks frame_monotonic_time,
    base::TimeDelta delayed_by)
{
    DCHECK(scroll_offset_animations_impl_);
    return scroll_offset_animations_impl_->ScrollAnimationUpdateTarget(
        element_id, scroll_delta, max_scroll_offset, frame_monotonic_time,
        delayed_by);
}

ScrollOffsetAnimations& AnimationHost::scroll_offset_animations() const
{
    DCHECK(scroll_offset_animations_);
    return *scroll_offset_animations_.get();
}

void AnimationHost::ScrollAnimationAbort(bool needs_completion)
{
    DCHECK(scroll_offset_animations_impl_);
    return scroll_offset_animations_impl_->ScrollAnimationAbort(needs_completion);
}

void AnimationHost::AddToTicking(scoped_refptr<AnimationPlayer> player)
{
    DCHECK(std::find(ticking_players_.begin(), ticking_players_.end(), player) == ticking_players_.end());
    ticking_players_.push_back(player);
}

void AnimationHost::RemoveFromTicking(scoped_refptr<AnimationPlayer> player)
{
    auto to_erase = std::find(ticking_players_.begin(), ticking_players_.end(), player);
    if (to_erase != ticking_players_.end())
        ticking_players_.erase(to_erase);
}

const AnimationHost::PlayersList& AnimationHost::ticking_players_for_testing()
    const
{
    return ticking_players_;
}

const AnimationHost::ElementToAnimationsMap&
AnimationHost::element_animations_for_testing() const
{
    return element_to_animations_map_;
}

} // namespace cc
